package com.ly.spring.mvc.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MyDispatchServlet extends HttpServlet{

    AnnotationConfigApplicationContext context;

    public MyDispatchServlet(AnnotationConfigApplicationContext context) {
        this.context = context;
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("req.getServletPath() = " + req.getServletPath());

        // 根据请求地址从IOC查找控制器方法
        Handler handler = searchHandlerMapping(req.getServletPath());
        System.out.println("handler = " + handler);

        // 执行控制器方法
        if(handler == null){
            // 判断是否是静态资源
            if(isStaticResource(req)){
                // 静态资源
                processStaticResource(req, resp);
            } else {
                // TODO 404
            }
        } else {
            // 执行控制器方法
            Object ret = executeHandler(handler, req, resp);
            // 处理控制器方法的返回结果 json , 页面跳转
            boolean isRestController = handler.getController().getClass().getAnnotation(RestController.class) != null;
            boolean isResponseBody = handler.getMethod().getAnnotation(ResponseBody.class)!=null;
            if (isRestController && ret instanceof ModelAndView == false
                    || isResponseBody) {
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(ret);
                resp.setContentType("application/json;charset=utf-8");
                resp.getWriter().println(json);
            } else {
                // 视图: String => 视图名  ModelAndView.getViewName
                String viewName = null;
                if(ret instanceof String){
                    viewName = (String) ret;
                } else if(ret instanceof ModelAndView){
                    viewName = ((ModelAndView) ret).getViewName();
                }
                // 跳转页面的方式: 请求转发, 响应重定向
                boolean isForward = false;
                if(viewName.startsWith("forward:")){
                    // 请求转发
                    isForward = true;
                    viewName = viewName.substring("forward:".length());
                } else if(viewName.startsWith("redirect:")){
                    // 响应重定向
                    viewName = viewName.substring("redirect:".length());
                } else {
                    // 请求转发
                    isForward = true;
                }

                if(isForward){
                    // 请求抓饭
                    RequestDispatcher dispatcher = req.getRequestDispatcher(viewName);
                    dispatcher.forward(req,resp);
                } else {
                    // 响应重定向
                    resp.sendRedirect(viewName);
                }

            }
        }
    }

    private void processStaticResource(HttpServletRequest req, HttpServletResponse resp) {
        String webpath = req.getServletPath();
        String filepath = req.getServletContext().getRealPath(webpath);
        try(FileInputStream fis = new FileInputStream(filepath)){
            ServletOutputStream out = resp.getOutputStream();
            byte[] bytes = new byte[1024];
            int count;
            while((count = fis.read(bytes)) > -1){
                out.write(bytes,0,count);
            }
        } catch (IOException e){
            throw new RuntimeException("读取资源文件错误!",e);
        }
    }

    private Object executeHandler(Handler handler,
                                  HttpServletRequest req,
                                  HttpServletResponse resp) {
        // 获取控制器方法的参数列表
        Parameter[] parameters = handler.method.getParameters();
        // 获取参数值
        List<Object> paramValues = new ArrayList<>();

        LocalVariableTableParameterNameDiscoverer lv = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = lv.getParameterNames(handler.method);

        int i = 0;
        for (Parameter parameter : parameters) {
            // String name = parameter.getName();
            String name = parameterNames[i++];
            String svalue = req.getParameter(name);
            // 参数类型
            Class<?> type = parameter.getType();
            Object ovalue = svalue;
            if(type.isAssignableFrom(Integer.class) || type.isAssignableFrom(int.class) ){
                ovalue = Integer.valueOf(svalue);
            } else if(type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class) ){
                ovalue = Boolean.valueOf(svalue);
            } else if(type.isAssignableFrom(String.class)){
                // 字符串不用处理
            } else if(type.isAssignableFrom(HttpServletRequest.class)){
                ovalue = req;
            } else if(type.isAssignableFrom(HttpServletResponse.class)){
                ovalue = resp;
            } else if(type.isAssignableFrom(HttpSession.class)){
                ovalue = req.getSession();
            } else if(type.isAssignableFrom(ModelAndView.class)){
                ovalue = new ModelAndView();
                // TODO 其他类型都处理完成之后, 剩下就是实体对象
            } else {
                // 实体对象 User
                // 使用反射将请求参数舍之道对象属性中
                try {
                    Object o = parameter.getType().newInstance();
                    for (Field field : o.getClass().getDeclaredFields()) {
                        String fieldName = field.getName();
                        String fieldSvalue = req.getParameter(fieldName);
                        if(fieldSvalue == null){
                            continue;
                        }
                        Object fieldOvalue = fieldSvalue;
                        if(field.getType().isAssignableFrom(int.class)){
                            fieldOvalue = Integer.valueOf(fieldSvalue);
                        } else {
                            // TODO 其他类型请自行扩展
                        }
                        field.setAccessible(true);
                        field.set(o, fieldOvalue);
                    }
                    ovalue = o;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            paramValues.add(ovalue);
        }
        // 执行控制器方法
        handler.method.setAccessible(true);
        try {
            Object ret = handler.method.invoke(handler.controller, paramValues.toArray());
            // 返回结果
            return ret;
        } catch (Exception e) {
            throw new RuntimeException("执行控制器方法错误",e);
        }
    }

    private boolean isStaticResource(HttpServletRequest req) {
        // webpath = /index.html
        String webpath = req.getServletPath();
        // filepath = F:\上课视频\143\yc143\spring\src\main\webapp\index.html
        // getRealPath 将资源的 web 路径 转为磁盘路径
        String filepath = req.getServletContext().getRealPath(webpath);
        // 判断文件是否存在
        return new File(filepath).exists();
    }

    private Handler searchHandlerMapping(String path) {
        Map<String, Object> restControllers = context.getBeansWithAnnotation(RestController.class);
        Map<String, Object> controllers = context.getBeansWithAnnotation(Controller.class);
        controllers.putAll(restControllers);
        // 搜索控制器方法 + 控制器对象
        for (Object controller : controllers.values()) {
            String prefixPath = null;
            RequestMapping requestMapping = controller.getClass().getAnnotation(RequestMapping.class);
            if(requestMapping != null){
                prefixPath = requestMapping.value()[0];
            }
            for (Method method : controller.getClass().getDeclaredMethods()) {
                String suffixPath;
                RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class);
                GetMapping getMapping = method.getAnnotation(GetMapping.class);
                PostMapping postMapping = method.getAnnotation(PostMapping.class);
                suffixPath = getMapping != null && getMapping.value().length > 0 ?
                        getMapping.value()[0] : null;
                // TODO 请自行扩展 RequestMapping PostMapping 以及 path 属性
                if(suffixPath != null){
                    String handlerPath ="/" + suffixPath;
                    if (prefixPath != null){
                        handlerPath = "/" + prefixPath + handlerPath;
                    }
                    if(handlerPath.equals(path)){
                        Handler handler = new Handler(method, controller);
                        return handler;
                    }
                }
            }
        }
        return null;
    }

    @AllArgsConstructor
    @Data
    class Handler {
        // 控制器方法
        Method method;
        // 控制器对象
        Object controller;
    }

}
